package validator

import (
	"testing"

	"github.com/stackrox/rox/central/vulnmgmt/vulnerabilityrequest/common"
	"github.com/stackrox/rox/generated/storage"
	"github.com/stretchr/testify/assert"
)

func TestValidateNewSuppressVulnRequest(t *testing.T) {
	req := &storage.VulnerabilityRequest{
		Comments: []*storage.RequestComment{
			{
				Message: "message",
			},
		},
		Status:      storage.RequestStatus_PENDING,
		TargetState: storage.VulnerabilityState_DEFERRED,
		Scope:       getImageScope("docker.io", "stackrox/main", "latest"),
		Entities: &storage.VulnerabilityRequest_Cves{
			Cves: &storage.VulnerabilityRequest_CVEs{
				Cves: []string{"cve1"},
			},
		},
		Req: &storage.VulnerabilityRequest_DeferralReq{
			DeferralReq: &storage.DeferralRequest{
				Expiry: &storage.RequestExpiry{
					Expiry: &storage.RequestExpiry_ExpiresWhenFixed{ExpiresWhenFixed: true},
				},
			},
		},
		Requestor: &storage.SlimUser{
			Id:   "user",
			Name: "user",
		},
		Expired: false,
	}

	// Correct request is valid
	assert.NoError(t, ValidateNewSuppressVulnRequest(req))

	// Cannot create a request in observed state
	cloned := req.CloneVT()
	cloned.TargetState = storage.VulnerabilityState_OBSERVED
	assert.EqualError(t, ValidateNewSuppressVulnRequest(cloned), "invalid arguments:  error: request to suppress vulnerability must be a deferral or false-positive request")

	// Cannot create an empty request
	cloned = req.CloneVT()
	cloned.Req = nil
	assert.EqualError(t, ValidateNewSuppressVulnRequest(cloned), "invalid arguments:  error: vulnerability deferral request invalid. Deferral expiry not provided")

	// Requests require comment
	cloned = req.CloneVT()
	cloned.Comments = nil
	assert.EqualError(t, ValidateNewSuppressVulnRequest(cloned), "invalid arguments:  error: vulnerability exception must have at least one comment")

	// Requests cannot start out approved
	cloned = req.CloneVT()
	cloned.Status = storage.RequestStatus_APPROVED
	assert.EqualError(t, ValidateNewSuppressVulnRequest(cloned), "invalid arguments:  error: new vulnerability exception must not be in approved state")

	// Requests cannot start out in APPROVED_PENDING_UPDATE state
	cloned = req.CloneVT()
	cloned.Status = storage.RequestStatus_APPROVED_PENDING_UPDATE
	assert.EqualError(t, ValidateNewSuppressVulnRequest(cloned), "invalid arguments:  error: new vulnerability exception must not be in approved state")

	// Cannot have an updated request
	cloned = req.CloneVT()
	cloned.UpdatedReq = &storage.VulnerabilityRequest_UpdatedDeferralReq{
		UpdatedDeferralReq: cloned.GetDeferralReq().CloneVT(),
	}
	assert.EqualError(t, ValidateNewSuppressVulnRequest(cloned), "invalid arguments:  error: expected new vulnerability exception, not an updated one")

	// Requestor must be set
	cloned = req.CloneVT()
	cloned.Requestor = nil
	assert.EqualError(t, ValidateNewSuppressVulnRequest(cloned), "invalid arguments:  error: vulnerability exception must have a requestor")
}

func TestValidateScope(t *testing.T) {
	// Empty image scope
	req := &storage.VulnerabilityRequest{
		Scope: &storage.VulnerabilityRequest_Scope{
			Info: &storage.VulnerabilityRequest_Scope_ImageScope{},
		},
	}
	assert.Error(t, validateScope(req))

	// No registry
	req = &storage.VulnerabilityRequest{
		Scope: getImageScope("", "stackrox/main", "latest"),
	}
	assert.Error(t, validateScope(req))

	// Invalid image name
	req = &storage.VulnerabilityRequest{
		Scope: getImageScope("docker.io", "+stackrox/main", "latest"),
	}
	assert.Error(t, validateScope(req))

	// Valid image name
	req = &storage.VulnerabilityRequest{
		Scope: getImageScope("docker.io", "stackrox-acs/main", "latest"),
	}
	assert.NoError(t, validateScope(req))

	// Invalid image tag
	req = &storage.VulnerabilityRequest{
		Scope: getImageScope("docker.io", "stackrox/main", "+3.60"),
	}
	assert.Error(t, validateScope(req))

	// Valid image tag
	req = &storage.VulnerabilityRequest{
		Scope: getImageScope("docker.io", "stackrox/main", "3.60"),
	}
	assert.NoError(t, validateScope(req))

	// Supported image tag regex
	req = &storage.VulnerabilityRequest{
		Scope: getImageScope("docker.io", "stackrox/main", ".*"),
	}
	assert.NoError(t, validateScope(req))

	// Empty image tag
	req = &storage.VulnerabilityRequest{
		Scope: getImageScope("docker.io", "stackrox/main", ""),
	}
	assert.NoError(t, validateScope(req))

	// v1 global scope
	req = &storage.VulnerabilityRequest{
		Scope: getGlobalScope(),
	}
	assert.NoError(t, validateScope(req))

	// v2 global scope
	req = &storage.VulnerabilityRequest{
		Scope: getImageScope(".*", ".*", ".*"),
	}
	assert.NoError(t, validateScope(req))
}

func TestUpdate(t *testing.T) {
	// no comment
	update := &common.UpdateRequest{
		DeferralUpdate: &storage.DeferralUpdate{
			CVEs:   []string{"cve-1"},
			Expiry: &storage.RequestExpiry{Expiry: &storage.RequestExpiry_ExpiresWhenFixed{ExpiresWhenFixed: true}},
		},
	}
	assert.Error(t, ValidateUpdate(update))

	// no cve
	update = &common.UpdateRequest{
		Comment: "update",
		DeferralUpdate: &storage.DeferralUpdate{
			Expiry: &storage.RequestExpiry{Expiry: &storage.RequestExpiry_ExpiresWhenFixed{ExpiresWhenFixed: true}},
		},
	}
	assert.Error(t, ValidateUpdate(update))

	// no message
	update = &common.UpdateRequest{
		Comment: "update",
	}
	assert.Error(t, ValidateUpdate(update))

	// valid deferral update
	update = &common.UpdateRequest{
		Comment: "update",
		DeferralUpdate: &storage.DeferralUpdate{
			CVEs:   []string{"cve-1"},
			Expiry: &storage.RequestExpiry{Expiry: &storage.RequestExpiry_ExpiresWhenFixed{ExpiresWhenFixed: true}},
		},
	}
	assert.NoError(t, ValidateUpdate(update))

	// valid false positive update
	update = &common.UpdateRequest{
		Comment: "update",
		FalsePositiveUpdate: &storage.FalsePositiveUpdate{
			CVEs: []string{"cve-1"},
		},
	}
	assert.NoError(t, ValidateUpdate(update))
}

func getImageScope(imageRegistry, imageName, tagRegex string) *storage.VulnerabilityRequest_Scope {
	return &storage.VulnerabilityRequest_Scope{
		Info: &storage.VulnerabilityRequest_Scope_ImageScope{
			ImageScope: &storage.VulnerabilityRequest_Scope_Image{
				Registry: imageRegistry,
				Remote:   imageName,
				Tag:      tagRegex,
			},
		},
	}
}

func getGlobalScope() *storage.VulnerabilityRequest_Scope {
	return &storage.VulnerabilityRequest_Scope{
		Info: &storage.VulnerabilityRequest_Scope_GlobalScope{
			GlobalScope: &storage.VulnerabilityRequest_Scope_Global{},
		},
	}
}
